using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml;
using Bizspoke.BizTalk.Adapter;
using Bizspoke.BizTalk.Brma;
using Bizspoke.BizTalk.HATPlugIn.BizTalk.Adapter;
using Bizspoke.BizTalk.HATPlugIn.Properties;
using Microsoft.BizTalk.Tracking;

namespace Bizspoke.BizTalk.HATPlugIn.BizTalk.Extension.HATPlugIn
{
    public enum TextBoxMode
    {
        Enter, Leave, None
    }

    public partial class MessageView : Form
    {
        private const string FormTitle = "HAT Message Viewer";

        private Color inActiveSearchColor = Color.Silver;
        private Color activeSearchColor = Color.DarkGray;
        private Font inActiveSearchFont = null;
        private Font activeSearchFont = null;

        private string searchText;
        private string xpathText;
        private string saveFolder;
        private string messageFileName = null;
        private string lastSelected = null;
        private int instanceIndex = 0;

        private TrackedInstance currentInstance;
        private ListViewColumnSorter lvwColumnSorter;

        private List<TrackedInstance> trackedInstances;
        private string outputFolder;

        public MessageView()
        {
            InitializeComponent();
            // Create an instance of a ListView column sorter and assign it 
            // to the ListView control.
            lvwColumnSorter = new ListViewColumnSorter();
            this.lvContext.ListViewItemSorter = lvwColumnSorter;
        }

        public MessageView(List<TrackedInstance> instances, string outputFolder)
        {
            InitializeComponent();
            // Create an instance of a ListView column sorter and assign it 
            // to the ListView control.
            lvwColumnSorter = new ListViewColumnSorter();
            this.lvContext.ListViewItemSorter = lvwColumnSorter;

            this.trackedInstances = instances;
            this.outputFolder = outputFolder;
        }

        #region Form - Control Event Handlers

        private void MessageView_Load(object sender, EventArgs e)
        {
            scInner.Dock = DockStyle.Fill;
            scLeft.Dock = DockStyle.Fill;
            //tcMessageContent.Dock = DockStyle.Fill;
            lvContext.Dock = DockStyle.Fill;
            lvMessages.Dock = DockStyle.Fill;
            pgInstanceDetails.Dock = DockStyle.Fill;
            tlpMessageContext.Dock = DockStyle.Fill;
            tlpInstance.Dock = DockStyle.Fill;
            tstxtSearch.Tag = tstxtSearch.Font;

            activeSearchFont = (Font)tstxtSearch.Font.Clone();
            tstxtSearch.Font = inActiveSearchFont = new Font((Font)(tstxtSearch.Font.Clone()), FontStyle.Italic);
            tstxtSearch.ForeColor = inActiveSearchColor;
            //tsMessageMenu.Enabled = false;

            wbMessageXmlView.Navigate("about:blank");
            lvContext.Items.Clear();

            if (trackedInstances.Count > 0)
            {
                for (int i = 0; i < trackedInstances.Count; i++)
                {
                    /*
                    if (TrackedInstances[i].TrackedMessages == null || TrackedInstances[i].TrackedMessages.Count == 0)
                        continue;

                    TrackedMessage trkmsg = TrackedInstances[i].TrackedMessages.Find(delegate(TrackedMessage tm) { return tm.Status == "Saved"; });

                    if (trkmsg.MessageInstanceId == null)
                    {
                        TrackedInstances.Remove(TrackedInstances[i]);
                        i = 0;
                    }
                    */

                    if (!trackedInstances[i].HasTrackedMessage)
                    {
                        trackedInstances.Remove(trackedInstances[i]);
                        i = 0;
                    }
                }

                if (trackedInstances.Count > 0)
                    LoadMessages(trackedInstances[0]);
            }

            tsbSaveMessages.Enabled = currentInstance.IsValid;
            tsbNext.Enabled = (trackedInstances.Count > 1);
            tsbLast.Enabled = (trackedInstances.Count > 1);
            //scMain.Visible = currentInstance.IsValid;
        }

        private void lvMessages_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lvMessages.SelectedItems.Count == 0)
                return;

            lastSelected = lvMessages.SelectedItems[0].Tag.GetType().Name;

            string messageInstanceId = null;

            if (lvMessages.SelectedItems[0].Tag is TrackedMessage)
            {
                messageInstanceId = ((TrackedMessage)lvMessages.SelectedItems[0].Tag).MessageInstanceId;
                /*MessageInfo messageInfo = ViewContext( ((TrackedMessage)lvMessages.SelectedItems[0].Tag).MessageInstanceId);
                if (messageInfo.PartInfo.PartsCount > 0)
                    ViewMessagePart((MessagePart)lvMessages.SelectedItems[0].Tag);*/
            }
            else if (lvMessages.SelectedItems[0].Tag is MessagePart)
            {
                messageInstanceId = ((MessagePart)lvMessages.SelectedItems[0].Tag).MessageInstanceId;
                /*ViewContext(((MessagePart)lvMessages.SelectedItems[0].Tag).MessageInstanceId);
                ViewMessagePart((MessagePart)lvMessages.SelectedItems[0].Tag);*/
            }

            if (!string.IsNullOrEmpty(messageInstanceId))
            {
                MessageInfo messageInfo = ViewContext(messageInstanceId);
                if (messageInfo.PartInfo.PartsCount > 0)
                    ViewMessagePart(messageInfo.PartInfo.MessageParts[0]);
            }

        }

        private void lvContext_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // Determine if clicked column is already the column that is being sorted.
            if (e.Column == lvwColumnSorter.SortColumn)
            {
                // Reverse the current sort direction for this column.
                if (lvwColumnSorter.Order == SortOrder.Ascending)
                {
                    lvwColumnSorter.Order = SortOrder.Descending;
                }
                else
                {
                    lvwColumnSorter.Order = SortOrder.Ascending;
                }
            }
            else
            {
                // Set the column number that is to be sorted; default to ascending.
                lvwColumnSorter.SortColumn = e.Column;
                lvwColumnSorter.Order = SortOrder.Ascending;
            }

            // Perform the sort with these new sort options.
            this.lvContext.Sort();
        }

        private void tcMessageContent_SelectedIndexChanged(object sender, EventArgs e)
        {
            SetSearchDetails(TextBoxMode.None);
        }

        #endregion

        #region Menu - Message Navigation Events

        private void tsbFirst_Click(object sender, EventArgs e)
        {
            if (this.instanceIndex > 0)
            {
                this.instanceIndex = 0;
                GetInstance();
            }
        }

        private void tsbLast_Click(object sender, EventArgs e)
        {
            if (this.instanceIndex < trackedInstances.Count - 1)
            {
                this.instanceIndex = trackedInstances.Count - 1;
                GetInstance();
            }
        }

        private void tsbPrevious_Click(object sender, EventArgs e)
        {
            if (this.instanceIndex > 0)
            {
                this.instanceIndex--;
                GetInstance();
            }
        }

        private void tsbNext_Click(object sender, EventArgs e)
        {
            if (this.instanceIndex < trackedInstances.Count - 1)
            {
                this.instanceIndex++;
                GetInstance();
            }
        }

        #endregion

        #region Menu - Misc Button Events

        private void tsbSaveMessages_Click(object sender, EventArgs e)
        {
            SaveMessages();
        }

        private void tsbOpenFolder_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start(saveFolder);
        }

        private void tsbOpenCurrentFile_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start(messageFileName);
        }

        private void tsbSearch_Click(object sender, EventArgs e)
        {
            switch (tcMessageContent.SelectedIndex)
            {
                case 0:
                    EvaluateXPath();
                    break;
                case 1:
                    FindText(tsmiMatchCase.Checked, tsmiMatchWholeWord.Checked);
                    break;
            }
        }

        private void tsbSearch_TextChanged(object sender, EventArgs e)
        {
            
        }

        private void tstxtSearch_Enter(object sender, EventArgs e)
        {
            SetSearchDetails(TextBoxMode.Enter);
        }

        private void tstxtSearch_Leave(object sender, EventArgs e)
        {
            SetSearchDetails(TextBoxMode.Leave);
        }

        #endregion

        #region Helper Methods

        private void LoadMessages(TrackedInstance trackedInstance)
        {
            currentInstance = trackedInstance;

            this.Text = string.Format("{0} - {1} ({2} of {3})", FormTitle, trackedInstance.ServiceInstanceId, this.instanceIndex + 1, this.trackedInstances.Count);

            lvMessages.Items.Clear();
            lvContext.Items.Clear();
            wbMessageXmlView.Navigate("about:blank");

            foreach (TrackingProperty prop in trackedInstance.TrackingProperties.Values)
            {
                System.Diagnostics.Trace.WriteLine("serviceInstance." + prop.Name + " = \"" + prop.Name + "\";");
            }

            if (trackedInstance.TrackingProperties.ContainsKey("Service/Name"))
                pgInstanceDetails.SelectedObject = new ServiceInstance(trackedInstance);
            else if (trackedInstance.TrackingProperties.ContainsKey("MessageInstance/SchemaName"))
                pgInstanceDetails.SelectedObject = new MessageInstance(trackedInstance);

            int imgIndex = imageList1.Images.IndexOfKey("Message16.ico");

            for (int i = 0; i < trackedInstance.TrackedMessages.Count; i++)
            {
                TrackedMessage trkmsg = trackedInstance.TrackedMessages[i];

                if (trkmsg.Status == "Saved")
                {
                    string messageInstanceId = FormatMessageId(trkmsg.MessageInstanceId);
                    MessageInfo messageInfo = LoadContext(messageInstanceId);
                    Property property = messageInfo.ContextInfo["MessageType"];

                    string msgtype = null;
                    if (property != null && !string.IsNullOrEmpty(property.Value))
                        msgtype = property.Value;
                    else
                        msgtype = messageInstanceId;

                    trkmsg.MessageType = msgtype;

                    ListViewItem lvItem = lvMessages.Items.Add(messageInstanceId, msgtype, imgIndex);
                    lvItem.Tag = trkmsg;

                    foreach (MessagePart part in messageInfo.PartInfo.MessageParts)
                    {
                        lvItem = lvMessages.Items.Add(messageInstanceId, "   " + part.Name, -1);
                        lvItem.Tag = part;
                    }
                }
            }
        }

        private void SaveMessages()
        {
            FolderBrowserDialog fbdlg = new FolderBrowserDialog();
            if (fbdlg.ShowDialog() == DialogResult.OK)
            {
                saveFolder = fbdlg.SelectedPath;

                foreach (TrackedMessage trkmsg in currentInstance.TrackedMessages)
                {
                    if (trkmsg.Status == "Saved")
                    {
                        string messageInstanceId = FormatMessageId(trkmsg.MessageInstanceId);
                        MessageInfo messageInfo = LoadContext(messageInstanceId);

                        string contextFileName = string.Format("{0}_context.xml", messageInstanceId);
                        File.Copy(Path.Combine(this.outputFolder, contextFileName), Path.Combine(fbdlg.SelectedPath, contextFileName), true);

                        foreach (MessagePart part in messageInfo.PartInfo.MessageParts)
                        {
                            string filename = Path.GetFileName(part.FileName);
                            File.Copy(part.FileName, Path.Combine(fbdlg.SelectedPath, filename), true);
                        }
                    }
                }
            }

            tsbOpenFolder.Enabled = !string.IsNullOrEmpty(saveFolder);
        }

        private void GetInstance()
        {
            LoadMessages(trackedInstances[this.instanceIndex]);

            if (lvMessages.Items.Count >= 1)
            {
                switch (lastSelected)
                {
                    case "TrackedMessage":
                        lvMessages.Items[0].Selected = true;
                        break;
                    case "MessagePart":
                        lvMessages.Items[1].Selected = true;
                        break;
                    default:
                        lvMessages.Items[0].Selected = true;
                        break;
                }
            }

            tsbFirst.Enabled = tsbPrevious.Enabled = (this.instanceIndex > 0);
            tsbLast.Enabled = tsbNext.Enabled = (this.instanceIndex < trackedInstances.Count - 1);

        }

        private MessageInfo LoadContext(string messageInstanceId)
        {
            string contextFileName = string.Format("{0}_context.xml", FormatMessageId(messageInstanceId));
            contextFileName = Path.Combine(this.outputFolder, contextFileName);

            MessageInfo messageInfo = null;

            if (File.Exists(contextFileName))
            {
                messageInfo = XmlHelper.Deserialize<MessageInfo>(contextFileName);
                messageInfo.MessageInstanceId = messageInstanceId;
            }

            return messageInfo;
        }

        private string FormatMessageId(string messageInstanceId)
        {
            return "{" + messageInstanceId.Replace("{", "").Replace("}", "") + "}";
        }

        #region View Methods

        private MessageInfo ViewContext(string messageInstanceId)
        {
            MessageInfo messageInfo = LoadContext(FormatMessageId(messageInstanceId));
            ViewContext(messageInfo);
            return messageInfo;
        }

        private void ViewContext(MessageInfo messageInfo)
        {
            lvContext.Items.Clear();
            foreach (Property property in messageInfo.ContextInfo.Properties)
            {
                ListViewItem lvItem = new ListViewItem(property.Name);
                lvItem.SubItems.Add(property.Value);
                if (property.Namespace != null)
                    lvItem.SubItems.Add(property.Namespace);
                lvContext.Items.Add(lvItem);
            }
        }

        private void ViewMessages(TrackedInstance instance)
        {
            foreach (TrackedMessage message in instance.TrackedMessages)
            {
                string msgtype = null;
                if (string.IsNullOrEmpty(message.MessageType))
                    msgtype = message.MessageType;

                ListViewItem lvItem = lvMessages.Items.Add(msgtype);
                lvItem.SubItems.Add(message.MessageInstanceId);
                lvItem.SubItems.Add(message.Size.ToString());
                lvItem.Tag = message;
            }
        }

        private void ViewMessageParts(PartInfo partInfo)
        {
            foreach (MessagePart part in partInfo.MessageParts)
            {
            }
        }

        private void ViewMessagePart(MessagePart part)
        {
            messageFileName = part.FileName;
            txtMessageTextView.Text = File.ReadAllText(messageFileName).Trim();

            if (txtMessageTextView.Text.StartsWith("<") && txtMessageTextView.Text.EndsWith(">"))
            {
                // assume message part is xml
                messageFileName = part.FileName + ".xml";
                if (!File.Exists(messageFileName))
                    File.Copy(part.FileName, messageFileName);
                wbMessageXmlView.Navigate(messageFileName);
                tsMessageMenu.Enabled = true;
            }
            else
            {
                tcMessageContent.SelectedIndex = 1;
                wbMessageXmlView.Navigate("about:blank");
                tsMessageMenu.Enabled = false;
            }

            tsbOpenCurrentFile.Enabled = File.Exists(messageFileName);
        }

        #endregion

        #endregion

        #region Text/XPath Find Methods

        private void SetSearchText(string text, Font font, Color color)
        {
            tstxtSearch.Text = text;
            tstxtSearch.Font = font;
            tstxtSearch.ForeColor = color;
        }

        private void SetSearchDetails(TextBoxMode mode)
        {
            switch (tcMessageContent.SelectedIndex)
            {
                case 0:
                    tslSearch.Text = "XPath:";
                    tsddbSearchOptions.Enabled = false;

                    switch (mode)
                    {
                        case TextBoxMode.Enter:
                            SetSearchText(xpathText, activeSearchFont, activeSearchColor);
                            break;
                        case TextBoxMode.Leave:
                            xpathText = tstxtSearch.Text;
                            if (string.IsNullOrEmpty(xpathText))
                                SetSearchText(Resources.DefaultXPathText, inActiveSearchFont, inActiveSearchColor);
                            break;
                        case TextBoxMode.None:
                            if (!string.IsNullOrEmpty(xpathText))
                                SetSearchText(xpathText, activeSearchFont, activeSearchColor);
                            else
                                SetSearchText(Resources.DefaultXPathText, inActiveSearchFont, inActiveSearchColor);
                            break;
                    }
                    break;
                case 1:
                    tslSearch.Text = "Find:";
                    tsddbSearchOptions.Enabled = true;

                    switch (mode)
                    {
                        case TextBoxMode.Enter:
                            SetSearchText(searchText, activeSearchFont, activeSearchColor);
                            break;
                        case TextBoxMode.Leave:
                            searchText = tstxtSearch.Text;
                            if (string.IsNullOrEmpty(searchText))
                                SetSearchText(Resources.DefaultSearchText, inActiveSearchFont, inActiveSearchColor);
                            break;
                        case TextBoxMode.None:
                            if (!string.IsNullOrEmpty(searchText))
                                SetSearchText(searchText, activeSearchFont, activeSearchColor);
                            else
                                SetSearchText(Resources.DefaultSearchText, inActiveSearchFont, inActiveSearchColor);
                            break;
                    }

                    /*
                    if ( tstxtSearch.Text != Resources.DefaultSearchText)
                        SetSearchText(searchText, (Font)tstxtSearch.Tag, Color.SlateGray);
                    if (string.IsNullOrEmpty(tstxtSearch.Text))
                        SetSearchText(Resources.DefaultSearchText, defaultSearchFont, defaultSearchColor);
                    else if (tstxtSearch.Text != Resources.DefaultSearchText)
                        SetSearchText(searchText, (Font)tstxtSearch.Tag, Color.SlateGray);
                    */
                    break;
            }
        }

        private void EvaluateXPath()
        {
            bool forward = true;
            bool matchWholeWord = false;
            bool matchCase = false;

            try
            {
                XmlDocument xmldoc = new XmlDocument();
                xmldoc.Load(messageFileName);
                XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmldoc.NameTable);

                string pattern = "xmlns[:]?(.*?)=\"(.*?)\"";
                MatchCollection matches = Regex.Matches(xmldoc.OuterXml, pattern, RegexOptions.IgnoreCase & RegexOptions.Multiline);

                foreach (Match match in matches)
                {
                    if (!nsmgr.HasNamespace(match.Groups[1].Value))
                        nsmgr.AddNamespace(match.Groups[1].Value, match.Groups[2].Value);
                }

                XmlNodeList nodes = xmldoc.SelectNodes(tstxtSearch.Text.Trim(), nsmgr);
                foreach (XmlNode node in nodes)
                {
                    EvaluateXPath(nodes, forward, matchWholeWord, matchCase, "#FFFF00");
                }
            }
            catch { }
        }

        private bool EvaluateXPath(XmlNodeList nodes, bool forward, bool matchWholeWord, bool matchCase, string color)
        {
            bool success = true;
            if (wbMessageXmlView.Document != null)
            {
                mshtml.IHTMLDocument2 doc = wbMessageXmlView.Document.DomDocument as mshtml.IHTMLDocument2;
                mshtml.IHTMLBodyElement body = doc.body as mshtml.IHTMLBodyElement;

                if (body != null)
                {
                    mshtml.IHTMLTxtRange range = null;
                    foreach (XmlNode node in nodes)
                    {
                        if (doc.selection != null)
                        {
                            range = doc.selection.createRange() as mshtml.IHTMLTxtRange;
                            mshtml.IHTMLTxtRange dup = range.duplicate();
                            dup.collapse(true);
                            // if selection is degenerate, then search whole body
                            if (range.isEqual(dup))
                            {
                                range = body.createTextRange();
                            }
                            else
                            {
                                if (forward)
                                    range.moveStart("character", 1);
                                else
                                    range.moveEnd("character", -1);
                            }
                        }
                        else
                            range = body.createTextRange();

                        int flags = 0;
                        if (matchWholeWord) flags += 2;
                        if (matchCase) flags += 4;
                        string startTag = null;
                        string endTag = null;
                        string closeTag = null;

                        if (node is XmlAttribute)
                        {
                            startTag = node.Name + "=\"";
                            flags += 2;
                        }
                        else if (node is XmlNode)
                        {
                            if (node.Attributes.Count == 0)
                                startTag = "<" + node.Name + ">";
                            else
                                startTag = "<" + node.Name;
                            endTag = ">";
                            closeTag = "</" + node.Name + ">";
                        }

                        success = range.findText(startTag, forward ? 999999 : -999999, flags);

                        if (!string.IsNullOrEmpty(endTag))
                        {
                            mshtml.IHTMLTxtRange endrange = range.duplicate();
                            success = endrange.findText(endTag, forward ? 999999 : -999999, flags);

                            if (success)
                                range.setEndPoint("EndToEnd", endrange);
                        }

                        if (node is XmlAttribute)
                            closeTag = "\"";

                        if (success)
                        {
                            range.select();
                            range.scrollIntoView(!forward);
                            wbMessageXmlView.Document.ExecCommand("BackColor", true, "#FFFF00");

                            flags = 0;
                            if (matchWholeWord) flags += 2;
                            if (matchCase) flags += 4;
                        }

                        if (success && !string.IsNullOrEmpty(node.InnerText))
                        {
                            success = range.findText(node.InnerText, forward ? 999999 : -999999, flags);

                            if (success)
                            {
                                range.select();
                                range.scrollIntoView(!forward);
                                wbMessageXmlView.Document.ExecCommand("BackColor", true, "#00FF00");
                            }
                        }

                        if (success)
                        {
                            success = range.findText(closeTag, forward ? 999999 : -999999, flags);

                            range.select();
                            range.scrollIntoView(!forward);
                            wbMessageXmlView.Document.ExecCommand("BackColor", true, "#FFFF00");
                        }

                    }
                    if (range != null)
                    {
                        range.collapse(true);
                        range.select();
                    }
                }
            }
            return success;
        }

        private void FindText(bool matchCase, bool matchWholeWord)
        {
            try
            {
                int pos = 0;
                txtMessageTextView.SelectAll();
                txtMessageTextView.SelectionBackColor = txtMessageTextView.BackColor;

                while (pos >= 0 && pos <= txtMessageTextView.Text.Length)
                {
                    pos = FindText(txtMessageTextView.Text, tstxtSearch.Text, pos, matchWholeWord, matchCase);
                    //pos = txtMessageTextView.Text.IndexOf(tstxtSearch.Text, pos);
                    if (pos >= 0)
                    {
                        Console.WriteLine("{0} : {1}", pos, txtMessageTextView.Text.Substring(pos - 5, tstxtSearch.Text.Length + 10));
                        txtMessageTextView.Select(pos, tstxtSearch.Text.Length);
                        txtMessageTextView.SelectionBackColor = Color.Yellow;
                        pos += tstxtSearch.Text.Length;
                    }
                }

                txtMessageTextView.SelectionLength = 0;

            }
            catch { }
        }

        private int FindText(string value, string searchText, int startIndex, bool wholeWord, bool matchCase)
        {
            int pos = startIndex;
            while (pos >= 0 && pos <= value.Length)
            {
                if (matchCase)
                    pos = value.IndexOf(searchText, pos);
                else
                    pos = value.IndexOf(searchText, pos, StringComparison.InvariantCultureIgnoreCase);

                if (pos >= 0)
                {
                    if (!wholeWord)
                        break;

                    // whole word search
                    string found = value.Substring(pos - 1, searchText.Length + 2);
                    if ((found[0] >= 65 && found[0] <= 122) || (found[found.Length - 1] >= 65 && found[found.Length - 1] <= 122))
                        pos += searchText.Length;
                    else
                        break;
                }
            }
            return pos;
        }

        #endregion

        private void tsbExpandCollapse_Click(object sender, EventArgs e)
        {
            if (!tsbExpandCollapse.Checked)
            {
                scInner.Panel2Collapsed = true;
                tsbExpandCollapse.ToolTipText = "Show Message Context";
            }
            else
            {
                scInner.Panel2Collapsed = false;
                tsbExpandCollapse.ToolTipText = "Hide Message Context";
            }
        }
    }
}